﻿#include "Decryptor.h"
#include "Eterfree/Concurrency/TaskQueue.h"
#include "Eterfree/Concurrency/ThreadPool.h"
#include "Eterfree/Core/SignalManager.h"

#include <cstdlib>
#include <optional>
#include <utility>
#include <memory>
#include <cmath>

using SizeType = Decryptor::SizeType;
using IDType = Decryptor::IDType;

static constexpr SizeType CONCURRENCY = 2;
static decltype(TABLE_SIZE) PASSWORD_LENGTH = 4;

static SizeType concurrency = 0;
static SizeType overlap = 0;

static void initialize(const Decryptor& _decryptor, SizeType _concurrency)
{
	auto counter = _decryptor.count();
	if (not counter) return;

	auto power = std::powl(TABLE_SIZE, PASSWORD_LENGTH);
	if (auto count = counter.value(); count < power)
	{
		concurrency = _concurrency;

		power = (power - count) / _concurrency;
		overlap = static_cast<decltype(overlap)>(std::ceill(power));
	}
}

static void execute(Decryptor& _decryptor, IDType _id)
{
	for (decltype(overlap) index = 0; \
		index < overlap; ++index)
	{
		if (_decryptor.complete()) break;

		_decryptor.execute(_id);
	}
}

static void handle(Decryptor& _decryptor, int)
{
	std::optional<SizeType> counter;
	for (decltype(concurrency) index = 0; \
		index < concurrency; ++index)
		if (auto size = _decryptor.count(index); \
			size and (not counter \
				or counter.value() > size.value()))
			counter = size;

	if (counter)
		_decryptor.close(counter.value());
}

int main()
{
	USING_ETERFREE_SPACE;

	using ThreadPool = Concurrency::ThreadPool;
	using TaskQueue = Concurrency::TaskQueue;

	Decryptor::Config config;
	config._length = PASSWORD_LENGTH;

	Decryptor decryptor;
	if (not decryptor.open(std::move(config)))
		return EXIT_FAILURE;

	auto threadPool = std::make_unique<ThreadPool>(CONCURRENCY);
	auto capacity = threadPool->getCapacity();
	initialize(decryptor, capacity);

	auto handler = [&decryptor](int _signal)
	{ handle(decryptor, _signal); };

	auto& manager = Singleton<SignalManager>::get();
	manager.insertObserver(SIGINT, 0, handler);
	manager.insertObserver(SIGILL, 0, handler);
	manager.insertObserver(SIGFPE, 0, handler);
	manager.insertObserver(SIGSEGV, 0, handler);
	manager.insertObserver(SIGTERM, 0, handler);
	manager.insertObserver(SIGABRT, 0, handler);

	auto taskQueue = std::make_shared<TaskQueue>(0);
	auto taskManager = threadPool->getTaskManager();
	if (taskManager != nullptr)
		taskManager->insert(taskQueue);

	for (decltype(capacity) index = 0; \
		index < capacity; ++index)
		taskQueue->put([&decryptor, index]
			{ execute(decryptor, index); });

	threadPool.reset();
	decryptor.close();
	return EXIT_SUCCESS;
}
